/*
 * config.c
 *
 *   Copyright 2010 Emediate ApS
 *
 *   This file is part of Stampede.
 *
 *   Stampede is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Foobar is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with Stampede.  If not, see <http://www.gnu.org/licenses/>.
 *
 *   Written by: Erland Lewin <erland@emediate.se>
 *
 * Configuration files:
 *
 * <Sessions>
 *   <Session>
 *     <Name>name</Name>
 *
 *     <Headers>
 *       <Header>
 *         <Name>Name</Name>
 *         <Value>Value</Value>
 *       </Header>
 *     </Headers>
 *     <Transactions>
 *       <Transaction>
 *         <ThinkTime>time in seconds</ThinkTime>
 *         <URL>url</URL>
 *         <ExpectedBodyFile>filename</ExpectedBodyFile>
 *       </Transaction>
 *       <Transaction>
 *       ...
 *       </Transaction>
 *     </Transactions>
 *   </Session>
 *   <Session>
 *   ...
 *   </Session>
 * </Sessions>
 *
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include <libxml/parser.h>
#include <libxml/tree.h>

#include "session.h"
#include "transaction.h"

typedef struct sConfiguration
{
   int users;
   int duration;

   int sessionCount;
   Session *sessions;
} sConfiguration, *Configuration;

static Session readSession( xmlNode *node, Configuration configuration );
static int readHeaders( xmlNode *headersNode, Header **headers );
static int readTransactions( xmlNode *transactionsNode,
                             Transaction **transactions );

Configuration config_read( const char *filename )
{
   // xmlParserCtxtPtr ctxt; /* the parser context */
   xmlDocPtr doc; /* the resulting document tree */
   xmlNode *root_element = NULL, *element;
   Configuration configuration;

   /*
    * this initialize the library and check potential ABI mismatches
    * between the version it was compiled for and the actual shared
    * library used.
    */
   LIBXML_TEST_VERSION

   configuration = malloc( sizeof( sConfiguration) );

   configuration->sessions = malloc( sizeof( Session ) * 10 ); // max 10 sessions for now
   configuration->sessionCount = 0;

   /*parse the file and get the DOM */
   doc = xmlReadFile(filename, NULL, XML_PARSE_NOBLANKS | XML_PARSE_DTDVALID );

   /*Get the root element node */
   root_element = xmlDocGetRootElement(doc);

   assert( strcmp( (char *) root_element->name, "Sessions") == 0 );

   for( element = root_element->children; element != NULL; element = element->next )
   {
      // if( element->type != XML_TEXT_NODE )
      // {
         configuration->sessions[ configuration->sessionCount ] =
               readSession( element, configuration );
         configuration->sessionCount++;
      // }
   }

   // printf( "element type: %d, name %s\n", root_element->type,  root_element->name );

   xmlFreeDoc(doc);

   return configuration;
#if 0
   /* create a parser context */
   ctxt = xmlNewParserCtxt();
   if (ctxt == NULL) {
       fprintf(stderr, "Failed to allocate parser context\n");
       return NULL;
   }

   /* parse the file, activating the DTD validation option */
   doc = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_DTDVALID);

   /* check if parsing suceeded */
   if (doc == NULL) {
       fprintf(stderr, "Failed to parse %s\n", filename);
   } else {
      /* check if validation suceeded */
      if (ctxt->valid == 0)
          fprintf(stderr, "Failed to validate %s\n", filename);
      /* free up the resulting document */
      xmlFreeDoc(doc);
   }
   /* free up the parser context */
   xmlFreeParserCtxt(ctxt);
#endif

#if 0
   FILE *file;
   int result;
   Session session;

   file = fopen( filename, "r" );

   result = gobbleTag( file, "<Sessions>");
   if( result != 0 )
      return NULL;

   session = readSession( file );
   if( session == NULL )
      return NULL;

   result = gobbleTag( file, "</Sessions>");
   if( result != 0 )
      return NULL;
#endif
}

static Session readSession( xmlNode *node, Configuration configuration )
{
   xmlNode *childNode;
   xmlChar *name;
   int headerCount;
   Header *headers = NULL;
   int transactionCount;
   Transaction *transactions = NULL;

   for( childNode = node->children; childNode != NULL;
         childNode = childNode->next )
   {
      if( strcasecmp( (char *) childNode->name, "name") == 0)
         name = childNode->children->content;
      else if( strcasecmp( (char *) childNode->name, "headers") == 0)
         headerCount = readHeaders( childNode, &headers );
      else if( strcasecmp( (char *) childNode->name, "transactions") == 0)
         transactionCount = readTransactions( childNode, &transactions );
      else
      {
         fprintf( stderr, "Got unexpected child node of sessions: '%s'\n", childNode->name );
         return NULL;
      }
   }

   return session_create( name, headerCount, headers, transactionCount, transactions );
#if 0
   result = gobbleTag( file, "<Session>");
   if( result != 0 )
      return NULL;

   /* read name tag */
   name = readInlineTag( file, "Name" );


   /* read a tag. It will be either header or Transactions */

   result = gobbleTag( file, "</Session>");
   if( result != 0 )
      return NULL;
#endif
}

static int readHeaders( xmlNode *headersNode, Header **headers )
{
   xmlNode *childNode;
   Header localHeaders[ 10 ]; // at most 10 headers for now
   int i, headerCount = 0;

   for( childNode = headersNode->children; childNode != NULL;
         childNode = childNode->next )
   {
      if( strcasecmp( (char *) childNode->name, "header") == 0 )
      {
         xmlChar *name;
         xmlChar *value;
         xmlNode *headerChildNode;

         for( headerChildNode = childNode->children; headerChildNode != NULL;
               headerChildNode = headerChildNode->next )
         {
            if( strcasecmp( (char *) headerChildNode->name, "name") == 0 )
               name = headerChildNode->children->content;
            else if( strcasecmp( (char *) headerChildNode->name, "value") == 0 )
               value = headerChildNode->children->content;
            else
            {
               fprintf( stderr, "Unexpected child node of header: %s\n", headerChildNode->name );
               return -1;
            }
         }

         localHeaders[ headerCount ] = header_create( name, value );

         headerCount++;
      }
      else
      {
         fprintf( stderr, "Got unexpected child node of headers: '%s'\n", childNode->name );
         return -1;
      }
   }

   *headers = malloc( sizeof( Header ) * headerCount );

   for( i = 0; i < headerCount; i++ )
      (*headers)[ i ] = localHeaders[ i ];

   return headerCount;

}

static int readTransactions( xmlNode *transactionsNode, Transaction **transactions )
{
   xmlNode *childNode;
   Transaction localTransactions[ 10 ]; // at most 10 headers for now
   int i, count = 0;

   for( childNode = transactionsNode->children; childNode != NULL;
         childNode = childNode->next )
   {
      if( strcasecmp( (char *) childNode->name, "transaction") == 0 )
      {
         xmlChar *url, *ebf;
         int thinkTime = 0;

         xmlNode *headerChildNode;

         for( headerChildNode = childNode->children; headerChildNode != NULL;
               headerChildNode = headerChildNode->next )
         {
            if( strcasecmp( (char *) headerChildNode->name, "thinktime") == 0 )
               thinkTime = atoi( (char *) headerChildNode->children->content );
            else if( strcasecmp( (char *) headerChildNode->name, "url") == 0 )
               url = headerChildNode->children->content;
            else if( strcasecmp( (char *) headerChildNode->name, "expectedBodyFilename") == 0 )
               ebf = headerChildNode->children->content;
            else
            {
               fprintf( stderr, "Unexpected child node of transaction: %s\n", headerChildNode->name );
               return -1;
            }
         }

         localTransactions[ count ] = transaction_create( url, thinkTime, ebf );

         if( localTransactions[ count ] != NULL )
            count++;
      }
      else
      {
         fprintf( stderr, "Got unexpected child node of transactions: '%s'\n", childNode->name );
         return -1;
      }
   }

   *transactions = malloc( sizeof( Transaction ) * count );

   for( i = 0; i < count; i++ )
      (*transactions)[ i ] = localTransactions[ i ];

   return count;
}

int config_getSessionCount( const Configuration config )
{
   return config->sessionCount;
}

#if 0
static char *readInlineTag( FILE *file, char *tagName )
{
   char buffer[1024], *charPtr, *charPtr2, *result;

   charPtr = readLine( file, buffer, sizeof( buffer ) );
   if( charPtr == NULL )
   {
      fprintf( stderr, "Failed to read inline tag '%s', got EOF or error\n", tagName );
      return NULL;
   }

   if( *charPtr != '<' )
   {
         fprintf( stderr, "Didn't get start of tag ('<'), got '%s'\n", charPtr );
         return NULL;
   }

   charPtr++;

   if( strncasecmp( charPtr, tagName, strlen( tagName )) != 0 )
   {
         fprintf( stderr, "Didn't get tag '%s', got '%s'\n", tagName, charPtr );
         return NULL;
   }
   charPtr += strlen( tagname );

   if( *charPtr != '>' )
   {
         fprintf( stderr, "Didn't get end of tag ('>'), got '%s'\n", charPtr );
         return NULL;
   }
   charPtr++;

   charPtr2 = strstr( charPtr, "</" );
   if( *charPtr2 == NULL )
   {
         fprintf( stderr, "Didn't find start of end tag ('</'), has '%s'\n", charPtr );
         return NULL;
   }

   if( strncasecmp( charPtr2 + 2, tagName, strlen( tagName )) != 0 )
   {
         fprintf( stderr, "Didn't get end tag '%s', got '%s'\n", tagName, charPtr2 + 2 );
         return NULL;
   }

   *charPtr2 = '\0';

   result = strdup( charPtr );

   return result;
}

static int gobbleTag( FILE *file, char *tagName )
{
   char buffer[ 1024 ], *charPtr;
   char *charPtr;

   charPtr = readLine( file, buffer, sizeof( buffer ) );
   if( charPtr == NULL )
   {
      fprintf( stderr, "No %s tag found\n". tagName );
      return 1;
   }

   if( strcasecmp( charPtr, tagName ) != 0 )
   {
      fprintf( stderr, "Expected %s, got %s", tagName, buffer );
      return 1;
   }

   return 0;
}

static char *readLine( FILE *file, char *buffer, size_t bufSize )
{
   int done = 0;

   do
   {
      charPtr = fgets( buffer, bufSize, file );
      if( charPtr == NULL )
         return NULL;
      else if( *charPtr == '#' )
         ; // ignore
      else
      {
         char *newLinePtr;

         while( isspace( *charPtr ))
            charPtr++;
         // skip whitespace at the beginning of the line
         // remove any newline

         newlinePtr = strchr( charPtr, '\n' );
         if( newLinePtr != NULL )
            *newLinePtr = '\0';
         return charPtr;
      }
   } while( 1 );
}
#endif

Session config_getSessionByIndex( const Configuration config, int sessionIndex )
{
   if( sessionIndex < config->sessionCount )
      return config->sessions[ sessionIndex ];
   else
      return NULL;
}
